#!/usr/bin/env python 
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import messagebox

from BasicMap import BasicMap
from InventoryView import InventoryView
from a2_solution import Inventory, Game, AdvancedTextInterface, advanced_game, HoldingPlayer, first_in_direction
from constants import *


class BasicGraphicalInterface():
    """
    Using pack() to organize UI
    From top to bottom: banner_frame, main_frame
    From left to right in main_frame: map_view, inventory_view
    """

    def __init__(self, root: tk.Tk, size: int):
        """
        The parameter root represents the root window and size
        represents the number of rows (= number of columns) in the game map. This method
        should draw the title label, and instantiate and pack the BasicMap and InventoryView.
        Args:
            root:
            size:
        """
        self.root = root
        self.size = size
        self.windows_width = size * CELL_SIZE + INVENTORY_WIDTH

        banner_frame = tk.Frame(self.root)
        self.init_banner(banner_frame)
        banner_frame.pack(fill=tk.BOTH)

        main_frame = tk.Frame(self.root)
        self.map_view = self.init_map_view(main_frame)
        self.inventory_view = InventoryView(main_frame, self.size)
        main_frame.pack(fill=tk.BOTH)

        self.after_id = None  # after method id, for start/stop timer
        self.app = None  # a2_solution.AdvancedTextInterface
        self.game = None  # when self.play call, store game here. For Restarting game

    def init_banner(self, banner_frame: tk.Frame):
        """
        abstract for polymorphism, compatible both in task1 and task2
        Args:
            banner_frame:

        Returns:

        """
        label = tk.Label(banner_frame, text=TITLE, font=FONT_TITLE, fg=WHITE, bg=DARK_PURPLE)
        label.pack(fill=tk.BOTH)

    def init_map_view(self, main_frame: tk.Frame):
        """
        abstract for polymorphism, compatible both in task1 and task2
        Args:
            main_frame:

        Returns:

        """
        return BasicMap(main_frame, (self.size, self.size))

    def _inventory_click(self, event: tk.Event, inventory: Inventory):
        """
        This method should be called when the user left clicks on inventory view.
        It must handle activating or deactivating the clicked item (if one exists)
        and update both the model and the view accordingly.
        Args:
            event:
            inventory:

        Returns:

        """
        self.inventory_view.toggle_item_activation((event.x, event.y), inventory)

    def draw(self, game: Game):
        """
        Clears and redraws the view based on the current game state.
        Args:
            game:

        Returns:

        """

        self.map_view.draw(game)
        player = game.get_player()
        inventory = player.get_inventory()
        self.inventory_view.draw(inventory)

    def _move(self, game: Game, direction: str):
        """
        Handles moving the player and redrawing the game. It may be easiest to create a new method
        to handle the ‘<KeyPress>’ event, which calls move with the relevant arguments.

        Args:
            game:
            direction:

        Returns:

        """
        self.app.handle_action(game, direction)
        self.draw(game)

    def _step(self, game: Game):
        """
        The step method is called every second. This method triggers the
        step method for the game and updates the view accordingly. Note: The .after method
        for tkinter widgets may be useful when trying to get this method to run every second.

        Args:
            game:

        Returns:

        """
        choice = self.end_game_choice(game)
        if not choice:
            game.step()
            self.draw(game)
            self.after_id = self.root.after(1000, self._step, game)

    def play(self, game: Game):
        """
        Binds events and initialises game play. This method will need to be
        called on the instantiated BasicGraphicalInterface in main to commence game play.
        Args:
            game:

        Returns:

        """
        self.game = game  # store in BasicGraphicalInterface
        self.app = AdvancedTextInterface(game.get_grid().get_size())
        self.draw(game)
        self.start_timer_event()

    def press_key_handler(self, event: tk.Event, game: Game):
        """
        key press handler, trigger self._move
        Args:
            event:
            game:

        Returns:

        """

        action = event.keysym.upper()
        if action in ['UP', 'DOWN', 'LEFT', 'RIGHT']:
            d = {'UP': 'W', 'DOWN': 'S', 'LEFT': 'A', 'RIGHT': 'D'}
            action = d.get(action)
            self.handle_fire_key(action, game)
        elif action in DIRECTIONS:
            self._move(game, action)

    def handle_fire_key(self, direction, game: Game):
        """
        handle crossbow fire,
        Args:
            direction:
            game:

        Returns:

        """
        player = game.get_player()
        inventory = player.get_inventory()
        if inventory.has_active(CROSSBOW):
            player = game.get_player()
            if player is None or not isinstance(player, HoldingPlayer):
                return  # Should never happen.

            start = game.get_grid().find_player()
            offset = game.direction_to_offset(direction)
            if start is None or offset is None:
                return  # Should never happen.

            # Find the first entity in the direction player fired.
            first = first_in_direction(
                game.get_grid(), start, offset
            )

            # If the entity is a zombie, kill it.
            if first is not None and first[1].display() in ZOMBIES:
                position, entity = first
                game.get_grid().remove_entity(position)
                self.draw(game)
            else:
                print(NO_ZOMBIE_MESSAGE)

    def click_handler(self, event: tk.Event, game: Game):
        """
        left click handler to trigger self._inventory_click
        Args:
            event:

        Returns:

        """
        player = game.get_player()
        inventory = player.get_inventory()
        self._inventory_click(event, inventory)

    def start_timer_event(self):
        """
        start every second timer and unbind all event
        Args:
            game:

        Returns:

        """
        self.root.bind('<Key>', lambda event: self.press_key_handler(event, self.game))
        self.inventory_view.bind('<Button-1>', lambda event: self.click_handler(event, self.game))
        self.root.after(1000, self._step, self.game)  # refresh game every second

    def stop_timer_event(self):
        """
        stop every second timer and unbind all event
        Returns:

        """
        self.root.after_cancel(self.after_id)
        self.root.unbind_all('<Key>')
        self.inventory_view.unbind_all('<Button-1>')

    def restart_game(self):
        """
        new an instance of Game, start to play
        Returns:

        """
        new_grid = advanced_game(MAP_FILE).get_grid()
        self.game.reset(new_grid)
        self.play(self.game)

    def end_game_choice(self, game):
        """
        if game ends, return choice(yes/no), else return None meaning current game continued
        Args:
            game:

        Returns:

        """
        won_game = game.has_won()
        lost_game = not won_game and game.has_lost()
        end_game = won_game or lost_game

        if end_game:
            self.stop_timer_event()
            msg = WIN_MESSAGE if won_game else LOSE_MESSAGE
            choice = messagebox.askquestion(msg, "Play again?")
            if choice == 'no':
                self.quit_game()
            elif choice == 'yes':
                self.restart_game()
                # new_grid = advanced_game(MAP_FILE).get_grid()
                # game.reset(new_grid)
                # self.play(game)
            else:
                print(choice)
            return choice
        return None

    def quit_game(self):
        """
        stop all event and timer, destroy tk
        Returns:

        """
        self.stop_timer_event()
        self.root.destroy()
